package com.xiaoyu.lanling.feature.voicematch.controller

import `in`.srain.cube.util.*
import `in`.srain.cube.util.general.GeneralUtil
import `in`.srain.cube.util.internal.AppCallback
import `in`.srain.cube.util.internal.AppConsumer
import `in`.srain.cube.util.internal.AppSupplier
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.SystemClock
import android.provider.Settings
import android.text.TextUtils
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.postprocessors.IterativeBoxBlurPostProcessor
import com.tbruyelle.rxpermissions2.RxPermissions
import com.xiaoyu.base.app.App
import com.xiaoyu.base.app.AppContext
import com.xiaoyu.base.app.GlobalUI
import com.xiaoyu.base.data.UserData
import com.xiaoyu.base.data.UserInfoDataProvider
import com.xiaoyu.base.model.User
import com.xiaoyu.base.utils.RxUtils
import com.xiaoyu.base.utils.extensions.setOnClickDebounceListener
import com.xiaoyu.base.utils.extensions.setViewTag
import com.xiaoyu.base.utils.time.CountDown
import com.xiaoyu.lanling.R
import com.xiaoyu.lanling.activity.base.BaseBottomSheetDialogFragment
import com.xiaoyu.lanling.common.Constant
import com.xiaoyu.lanling.data.NotificationData
import com.xiaoyu.lanling.feature.voicecall.data.CallData
import com.xiaoyu.lanling.feature.voicecall.model.ActionMessageType
import com.xiaoyu.lanling.feature.voicecall.model.HangupReason
import com.xiaoyu.lanling.feature.voicecall.model.MessageType
import com.xiaoyu.lanling.feature.voicecall.model.SystemMessageType
import com.xiaoyu.lanling.feature.main.activity.MainActivity
import com.xiaoyu.lanling.feature.voicematch.VoiceCallFloatWindowManager
import com.xiaoyu.lanling.feature.voicematch.VoiceMatchEvaluateDialogFragment
import com.xiaoyu.lanling.feature.voicematch.activity.VoiceMatchCallActivity
import com.xiaoyu.lanling.feature.voicematch.data.VoiceMatchData
import com.xiaoyu.lanling.feature.voicematch.log.VoiceMatchLogger
import com.xiaoyu.lanling.feature.voicematch.util.VoiceMatchNotificationUtils
import com.xiaoyu.lanling.media.image.ImageViewUtil
import com.xiaoyu.lanling.media.image.UserImageLoadParam
import com.xiaoyu.lanling.router.Router
import com.xiaoyu.lanling.util.TextViewUtil
import com.xiaoyu.lib_av.datamodel.*
import com.xiaoyu.lib_av.datamodel.CallHangUpType.NEGATIVE
import com.xiaoyu.lib_av.datamodel.CallHangUpType.POSITIVE
import com.xiaoyu.lib_av.listener.AbstractVoiceCallLifecycleListener
import com.xiaoyu.lib_av.manager.CallManager
import com.xiaoyu.net.util.NetUtils
import com.yhao.floatwindow.PermissionUtil
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.subjects.PublishSubject
import kotlinx.android.synthetic.main.voice_match_activity.*
import java.util.concurrent.TimeUnit

class VoiceMatchCallViewController(val activity: FragmentActivity) : AbstractVoiceCallLifecycleListener(), SensorEventListener, BaseBottomSheetDialogFragment.OnDestroyListener {

    companion object {
        const val CHARGE_START_DURATION = 10
    }

    private var mMainTaskTopActivity: Activity? = null

    private val mCallParams: CallParams?
    private val mIsCaller: Boolean
    private val mFuid: String?
    private var mCallDurationInS: Long = 0
    private var mWifiOffPublishSubject: PublishSubject<Boolean>? = null
    private val mNotificationDisposable: Disposable? = null
    private var mDisplayUserDisposable: Disposable? = null
    private var mWifiDisposable: Disposable? = null
    private var mCountDownDisposable: Disposable? = null
    private var mIsHeadset = false
    private var mDisableAudioStartTime = 0L

    private val onAudioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { }

    init {
        mCallParams = activity.intent.getSerializableExtra(Constant.IntentKey.KEY_CHAT_CALL_CALL_PARAM) as? CallParams
        mIsCaller = activity.intent.getBooleanExtra(Constant.IntentKey.KEY_CHAT_CALL_IS_CALLER, false)
        mFuid = mCallParams?.getFuid(UserData.getInstance().isSelf(mCallParams.fromUser.uid))

        if (mFuid == null) {
            activity.finishAndRemoveTask()
        } else {
            CallManager.instance.create(this)

            initView()
            initBind()
            initBaseSupport()
            initPermissions()
            initHeadset()
        }
    }

    private fun initView() {
        activity.background.layoutParams.width = LocalDisplay.SCREEN_WIDTH_PIXELS
        activity.background.layoutParams.height = LocalDisplay.SCREEN_HEIGHT_PIXELS
    }

    fun onResume() {
        mMainTaskTopActivity = App.getInstance().topActivity
        dismissCallNotification()
        VoiceCallFloatWindowManager.instance.hideAndStopFloatWindow()
    }

    fun onStop() {
        if (isCalling) {
            if (!TextUtils.isEmpty(mFuid)) {
                showCallNotification()
            }
            activity.voice_time?.let {
                GeneralUtil.runFromBackgroundToMain(AppSupplier<User> {
                    UserInfoDataProvider.getInstance().ensureAndGetUser(mFuid)
                }, AppConsumer { user ->
                    if (activity.voice_time?.visibility == View.VISIBLE) {
                        VoiceCallFloatWindowManager.instance.showFloatWindow(activity, it.base, true, user)
                    } else {
                        VoiceCallFloatWindowManager.instance.showFloatWindow(activity, -1, true, user)
                    }
                })
            }
        }
    }

    fun onDestroy() {
        RxUtils.disposable(mNotificationDisposable, mDisplayUserDisposable, mWifiDisposable, mCountDownDisposable)
        stopTime()
        mWifiOffPublishSubject?.onComplete()
        dismissCallNotification()
        VoiceCallFloatWindowManager.instance.hideAndStopFloatWindow()
        CallManager.instance.destroy()
    }

    fun onBackPressed() {
        if (!PermissionUtil.hasPermission(activity)) {
            AlertDialog.Builder(activity)
                    .setTitle(R.string.float_window_permission_dialog_title)
                    .setMessage(R.string.float_window_permission_dialog_message)
                    .setPositiveButton(R.string.float_window_permission_dialog_positive_button) { _: DialogInterface?, _: Int -> requestAlertWindowPermission() }
                    .show()
            return
        }
        val topActivity = mMainTaskTopActivity
        val topIntent: Intent? = topActivity?.intent
        if (topActivity == null || topActivity is VoiceMatchCallActivity) {
            val intent = Intent(activity, MainActivity::class.java)
            activity.startActivity(intent)
        } else if (topIntent != null) {
            topIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
            activity.startActivity(topIntent)
        }
    }

    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == Constant.RequestCode.SETTINGS_OVERLAY_PERMISSION) {
            if (PermissionUtil.hasPermissionOnActivityResult(activity)) {
                VoiceCallFloatWindowManager.instance.destroyFloatWindow(activity)
            } else {
                activity.user_avatar?.postDelayed({ GlobalUI.getInstance().showToast(R.string.float_window_permission_mission_toast) }, 250)
            }
        }
    }

    private fun initBind() {
        activity.voice_mute?.setOnClickDebounceListener { activity.voice_mute?.let { CallManager.instance.setMute(it.isChecked) } }
        activity.voice_hands_free?.setOnClickDebounceListener { CallManager.instance.isEnableSpeaker = activity.voice_hands_free?.isChecked }
        activity.voice_cancel?.setOnClickDebounceListener {
            if (TextUtils.isEmpty(mFuid)) {
                return@setOnClickDebounceListener
            }
            sendCallMessage()
            hangup(HangupReason.HANGUP)
        }
        activity.voice_fullscreen_exit?.setOnClickDebounceListener {
            VoiceMatchLogger.shrink()
            onBackPressed()
        }
        activity.charge_button?.setOnClickDebounceListener {
            Router.instance.gotoCoinProductListAndBalanceActivity(activity)
            activity.charge_layout.visibility = View.GONE
            mCountDownDisposable?.dispose()
        }
    }

    @SuppressLint("CheckResult")
    private fun initBaseSupport() {
        mWifiOffPublishSubject = PublishSubject.create()
        mWifiOffPublishSubject?.let {
            mWifiDisposable = it
                    .throttleFirst(2000, TimeUnit.MILLISECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe({ GlobalUI.getInstance().showToast(R.string.voice_match_call_wifi_off_toast) }) { initBaseSupport() }
        }
        if (!NetworkStatusManager.getInstance().isWifi) {
            mWifiOffPublishSubject?.onNext(true)
        }
    }

    @SuppressLint("CheckResult")
    private fun initPermissions() {
        if (activity.isFinishing || activity.isDestroyed) {
            return
        }
        RxPermissions(activity)
                .request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .subscribe({ granted: Boolean ->
                    if (granted) {
                        // checkNotificationPermission()
                        processData()
                    } else {
                        GlobalUI.getInstance().showToast(R.string.voice_match_call_permission_miss_toast)
                        finish()
                    }
                }) { obj: Throwable -> obj.printStackTrace() }
    }

    private fun initHeadset() {
        val audioManager = AppContext.getContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
                ?: return
        audioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
        if (audioManager.isWiredHeadsetOn || audioManager.isBluetoothA2dpOn) {
            headsetOn()
        } else {
            headsetOff()
        }
    }

    override fun onReceiveCall(fromUserId: String, params: CallParams, runnable: Runnable?) {
    }

    override fun onCallFailed(fuid: String, errorType: String) {
        when (errorType) {
            CallFailType.NO_RESPONSE -> GlobalUI.getInstance().showToast("对方暂不方便接听")
            CallFailType.ILLEGAL_ARGUMENT -> GlobalUI.getInstance().showToast("参数错误")
            CallFailType.NOT_LOGIN -> GlobalUI.getInstance().showToast("暂未连接上服务器，请稍后再试")
            CallFailType.UNKNOWN -> GlobalUI.getInstance().showToast("未知错误")
        }
        VoiceMatchLogger.callFailure(errorType)
        hangup(errorType)
        finish()
    }

    override fun onRespondCall(fuid: String, responseType: String) {
    }

    override fun onCallResponded(fuid: String, responseType: String) {
        when (responseType) {
            CallResponseType.BUSY_LINE, CallResponseType.REFUSE -> {
                GlobalUI.getInstance().showToast("对方暂不方便接听")
                VoiceMatchLogger.callFailure(responseType)
                hangup(responseType)
                finish()
            }
            CallResponseType.ACCEPT -> {
            }
        }
    }

    override fun onCancelCall(fuid: String) {
        VoiceMatchLogger.callFailure("cancel")
        finish()
    }

    override fun onCallCanceled(callParams: CallParams) {
        finish()
    }

    override fun onCallConnecting(fuid: String) {
    }

    override fun onCallConnected(fuid: String) {
        startTime()
        connectedWithServer(fuid)
    }

    override fun onCallDisconnected(fuid: String, @CallHangUpType.HangUpTypeDef hangupType: String) {
        when (hangupType) {
            NEGATIVE -> {
                VoiceMatchLogger.hangup(CallHangUpType.NEGATIVE, mCallDurationInS, HangupReason.OTHER_DISCONNECT)
                VoiceMatchData.hangup(mFuid, HangupReason.UNKNOWN)
                showEvaluateDialogOrFinish()
            }
            POSITIVE -> {
                showEvaluateDialogOrFinish()
            }
            else -> {
                finish()
            }
        }
    }

    override fun onNetStatusReported(good: Boolean, uid: String) {
        if (good) {
            dismissPoorNetworkStatus()
        } else {
            showPoorNetworkStatus()
        }
    }

    override fun onAudioDeviceChanged(headset: Boolean) {
        if (headset) {
            headsetOn()
        } else {
            headsetOff()
        }
    }

    override fun onStatus(totalDurationInS: Long) {
        mCallDurationInS = totalDurationInS
    }

    override fun onMessageSend(message: CallMessage) {
    }

    override fun onMessageReceive(message: CallMessage) {
        when (message.getType()) {
            MessageType.ACTION -> onActionMessageReceive(message)
            MessageType.SYSTEM -> onSystemMessageReceive(message)
        }
    }

    override fun onTokenExpired() {
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
    }

    override fun onSensorChanged(event: SensorEvent?) {
    }

    private fun hangup(reason: String) {
        VoiceMatchLogger.hangup(POSITIVE, mCallDurationInS, reason)
        VoiceMatchData.hangup(mFuid, reason)
        CallManager.instance.hangUp(mFuid, POSITIVE)
    }

    private fun showEvaluateDialogOrFinish() {
        val self = UserData.getInstance().user
        if (self.isMale && mCallDurationInS >= CHARGE_START_DURATION) {
            if (activity.isDestroyed || activity.isFinishing) {
                return
            }
            val user = UserInfoDataProvider.getInstance().ensureAndGetUser(mFuid)
            VoiceMatchEvaluateDialogFragment.show(activity.supportFragmentManager, user, this)
        } else {
            finish()
        }
    }

    private fun receive() {
        CallManager.instance.response(mFuid, CallData.ActionType.ACCEPT)
    }

    private fun requestAlertWindowPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
            intent.data = Uri.parse("package:" + activity.packageName)
            if (CheckUtil.checkAppsIsExist(activity, intent)) {
                activity.startActivityForResult(intent, Constant.RequestCode.SETTINGS_OVERLAY_PERMISSION)
            } else {
                GlobalUI.getInstance().showToast(activity.getString(R.string.voice_match_call_overlay_permission_miss_toast))
            }
        }
    }

    private val isCalling: Boolean
        get() = CallManager.instance.isCalling

    private fun sendCallMessage() {
        val hasConnect = mCallDurationInS > 0
        mFuid?.let {
            if (hasConnect) {
                val duration = mCallDurationInS.toInt()
                CallData.sendVoiceStatusMessageForCall(it, duration.toLong(), Constant.From.VOICE_MATCH)
            } else {
                if (mIsCaller) {
                    CallData.sendVoiceStatusMessageForCancel(it)
                } else {
                    CallData.sendVoiceStatusMessageForReject(it)
                }
            }
        }
    }

    private fun processData() {
        if (!NetUtils.isConnected(AppContext.getContext(), true)) {
            finish()
            return
        }
        if (TextUtils.isEmpty(mFuid)) {
            finish()
            return
        }
        showUserData(mFuid)
        processStatus()
    }

    private fun processStatus() {
        activity.voice_status?.visibility = View.VISIBLE
        activity.voice_call_layout?.visibility = View.VISIBLE
        if (mIsCaller) {
            activity.voice_status?.text = activity.getString(R.string.voice_match_call_waiting_receipt)
            call()
        } else {
            activity.voice_status?.text = activity.getString(R.string.voice_match_call_connecting)
            receive()
        }
    }

    private fun call() {
        mCallParams?.let {
            VoiceMatchLogger.callStart()
            CallManager.instance.call(it, object : AppCallback<Boolean> {
                override fun onSuccess(t: Boolean) {
                    VoiceMatchLogger.callSuccess()
                }

                override fun onError(e: Throwable) {
                    VoiceMatchLogger.callFailure("sdk_error")
                    finish()
                }
            })
        }
    }

    @SuppressLint("CheckResult")
    private fun showUserData(uid: String?) {
        mDisplayUserDisposable = Completable.fromAction { UserInfoDataProvider.getInstance().ensureUser(uid) }
                .compose(RxUtils.ioToMainCompletableScheduler<Any>())
                .subscribe {
                    val user: User = UserInfoDataProvider.getInstance().fetchFromMemory(uid)
                    ImageViewUtil.loadAvatarRoundWithBoard(activity.user_avatar, user, 96, 2, R.color.user_info_avatar_border, true)
                    loadBackground(activity.background, user)
                    activity.user_nickname?.setUser(user)
                }
    }

    private fun headsetOn() {
        if (mIsHeadset) {
            return
        }
        mIsHeadset = true
        activity.voice_hands_free?.isEnabled = false
        activity.voice_hands_free?.alpha = 0.5f
        activity.voice_hands_free?.isChecked = false
        activity.voice_hands_free_title?.alpha = 0.5f
        CallManager.instance.isEnableSpeaker = false
    }

    private fun headsetOff() {
        if (!mIsHeadset) {
            return
        }
        mIsHeadset = false
        activity.voice_hands_free?.isEnabled = true
        activity.voice_hands_free?.alpha = 1f
        activity.voice_hands_free_title?.alpha = 1f
    }

    private fun showPoorNetworkStatus() {
        activity.voice_network_status?.visibility = View.VISIBLE
    }

    private fun dismissPoorNetworkStatus() {
        activity.voice_network_status?.visibility = View.GONE
    }

    private fun checkNotificationPermission() {
        val notificationEnable: Boolean = CheckUtil.isNotificationChannelEnabled(activity, NotificationData.GroupId.CALL, NotificationData.ChannelId.CALL_VOICE_MATCH)
        if (!notificationEnable) {
            showNotificationPermissionDialog()
        }
    }

    private fun showNotificationPermissionDialog() {
        AlertDialog.Builder(activity)
                .setMessage(R.string.voice_match_call_permission_dialog_message)
                .setPositiveButton(R.string.action_confirm) { _, _ -> Router.instance.gotoNotificationSettings() }
                .show()
    }

    private fun showCallNotification() {
        mCallParams?.let {
            VoiceMatchNotificationUtils.instance.showVoiceMatchNotification(it, mIsCaller, false)
        }
    }

    private fun dismissCallNotification() {
        VoiceMatchNotificationUtils.instance.dismissCallNotification()
    }

    private fun startTime() {
        activity.voice_status?.visibility = View.GONE
        activity.voice_time?.visibility = View.VISIBLE
        activity.voice_time?.let {
            it.base = SystemClock.elapsedRealtime()
            it.start()
            VoiceCallFloatWindowManager.instance.showFloatWindow(activity, it.base, false, null)
        }
    }

    private fun stopTime() {
        activity.voice_time?.stop()
    }

    private fun loadBackground(avatar: SimpleDraweeView?, user: User) {
        avatar?.setViewTag(Constant.GlobalViewTagDefine.VIEW_TAG_FOR_USER, user)
        val thumbnailParam = UserImageLoadParam.newBuilder()
                .setUser(user, 96, 96) // same size with the avatar in R.layout.item_main_conversation
                .setPostProcessor(IterativeBoxBlurPostProcessor(2, 4))
                ?.setOverlayResId(R.drawable.chat_call_background_overlay_shape)
                ?.build()
        val param = UserImageLoadParam.newBuilder()
                .setUser(user, LocalDisplay.SCREEN_WIDTH_DP, LocalDisplay.SCREEN_HEIGHT_DP)
                .setPostProcessor(IterativeBoxBlurPostProcessor(2, 4))
                ?.setOverlayResId(R.drawable.chat_call_background_overlay_shape)
                ?.setLowResImageLoadParam(thumbnailParam)
                ?.build()
        ImageViewUtil.loadImage(avatar, param)
    }

    private fun finish() {
        stopTime()
        activity.finishAndRemoveTask()
    }

    private fun connectedWithServer(fuid: String) {
        if (mIsCaller) {
            VoiceMatchLogger.connectSuccess()
            VoiceMatchData.establishConnection(fuid)
        }
    }

    private fun onActionMessageReceive(message: CallMessage) {
        when (message.attrs?.optString("actionType")) {
            ActionMessageType.HANGUP -> {
                if (mIsCaller) {
                    sendCallMessage()
                }
                hangup(HangupReason.COIN_NOT_ENOUGH)
            }
        }
    }

    private fun onSystemMessageReceive(message: CallMessage) {
        when (message.attrs?.optString("subtype")) {
            SystemMessageType.EARNING_TIP -> {
                TextViewUtil.displayOrGone(activity.earning_tip, message.attrs?.optString("earningDesc"))
            }
            SystemMessageType.COIN_NOT_ENOUGH -> {
                val countDown = CountDown.createFromJson(message.attrs?.optJson("countDown"))
                activity.charge_layout.visibility = if (countDown.shouldDoUpdateByServers()) View.VISIBLE else View.GONE
                mCountDownDisposable?.dispose()
                mCountDownDisposable = Observable.interval(0, 1, TimeUnit.SECONDS)
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe {
                            if (!countDown.shouldDoUpdateByServers()) {
                                mCountDownDisposable?.dispose()
                                return@subscribe
                            }
                            activity.count_down_desc.text = activity.getString(R.string.call_coin_not_enough_count_down_desc, countDown.digitalCountDownTextByServers)
                        }
            }
        }
    }

    override fun onDialogDestroy() {
        finish()
    }

    override fun onRemoteAudioMuteStateChanged(fuid: String, mute: Boolean) {
        val user = UserInfoDataProvider.getInstance().ensureAndGetUser(fuid)
        updateAudioStatusForFemale(user, mute)
    }

    override fun onLocalAudioMuteStatusChanged(mute: Boolean) {
        updateAudioStatusForFemale(UserData.getInstance().user, mute)
        logEventForAudioDisable(mute)
    }

    private fun updateAudioStatusForFemale(user: User, mute: Boolean) {
        if (!user.isMale) {
            activity.audio_status?.visibility = if (mute) View.VISIBLE else View.GONE
            activity.audio_status?.text = if (user.isSelf) activity.getString(R.string.voice_match_audio_status_tip_female_disable_self) else activity.getString(R.string.voice_match_audio_status_tip_female_disable_other)
        }
    }

    private fun logEventForAudioDisable(disable: Boolean) {
        if (disable) {
            mDisableAudioStartTime = System.currentTimeMillis()
            VoiceMatchLogger.disableAudio()
        } else {
            val duration = (System.currentTimeMillis() - mDisableAudioStartTime) / 1000
            VoiceMatchLogger.enableAudio(duration)
            mDisableAudioStartTime = 0
        }
    }
}